package com.liry.mybatis.datascope.interceptor;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.liry.mybatis.datascope.DataScopeUtil;
import com.liry.mybatis.datascope.MybatisPlusUtil;
import com.liry.mybatis.datascope.model.DataColumnProperty;
import com.liry.mybatis.datascope.model.DataScopeProperty;
import com.liry.mybatis.datascope.model.DataScopeSqlSource;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SubSelect;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 数据权限拦截器
 *
 * @author ALI
 * @since 2022/9/13
 */
public class DataScopeInnerInterceptor {

    private static Logger log = LoggerFactory.getLogger(DataScopeInnerInterceptor.class);

    /**
     * 重新渲染sql对象
     *
     * @param args     原始方法参数
     * @param ms       mapper声明
     * @param boundSql 绑定的sql对象
     */
    public static void render(Object[] args, MappedStatement ms, BoundSql boundSql) {
        try {
            DataScopeProperty dataScopeProperty = DataScopeUtil.getDataScopeProperty(ms.getId());

            if (dataScopeProperty != null && dataScopeProperty.isIgnore()) {
                return;
            }

            DataScopeProperty likeProperty = DataScopeUtil.getGlobalLikeProperty();
            DataScopeProperty threadDataScopeProperty = DataScopeUtil.getThreadDataScopeProperty();

            Statements statements = CCJSqlParserUtil.parseStatements(boundSql.getSql());

            StringBuilder sb = new StringBuilder();
            for (Statement statement : statements.getStatements()) {
                if (statement instanceof Select) {
                    PlainSelect plainSelect = (PlainSelect) ((Select) statement).getSelectBody();
                    where(plainSelect, args, dataScopeProperty);

                    if (plainSelect.getFromItem() instanceof Table) {
                        tableSelectBuild(plainSelect, args, likeProperty, threadDataScopeProperty);
                    } else if (plainSelect.getFromItem() instanceof SubSelect && ms.getId().endsWith("_mpCount")) {
                        // 分页，且还是子查询
                        SubSelect subSelect = (SubSelect) plainSelect.getFromItem();
                        PlainSelect subPlainSelect = (PlainSelect) subSelect.getSelectBody();
                        tableSelectBuild(subPlainSelect, args, likeProperty, threadDataScopeProperty);
                    }
                    sb.append(plainSelect);
                }
            }

            MappedStatement newMs = buildMappedStatement(ms, ms.getSqlCommandType(), sb.toString(), boundSql);
            // 将旧的声明替换
            args[0] = newMs;
            if (args.length != 4) {
                args[5] = newMs.getBoundSql(newMs);
            }
        } catch (ClassNotFoundException | JSQLParserException e) {
            log.error("解析mapper数据权限信息失败！", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 构建selectbody
     *
     * @param plainSelect             select 对象
     * @param args                    实际参数
     * @param likeProperty            全局参数对象
     * @param threadDataScopeProperty 全局线程参数对象
     */
    private static void tableSelectBuild(PlainSelect plainSelect, Object[] args, DataScopeProperty likeProperty,
                                         DataScopeProperty threadDataScopeProperty) {
        if (plainSelect.getFromItem() instanceof Table) {
            String tableName = ((Table) plainSelect.getFromItem()).getName();
            List<String> tableColumns = MybatisPlusUtil.getTableColumns(tableName);

            // 不确定的字段
            if (likeProperty != null) {
                for (DataColumnProperty column : likeProperty.getColumns()) {
                    if (tableColumns.contains(column.getName())) {
                        where(plainSelect, args, new DataScopeProperty(column));
                        break;
                    }
                }
            }
            // 当前线程
            if (threadDataScopeProperty != null) {
                for (DataColumnProperty column : threadDataScopeProperty.getColumns()) {
                    if (tableColumns.contains(column.getName())) {
                        where(plainSelect, args, new DataScopeProperty(column));
                        break;
                    }
                }
            }
        }
    }

    /**
     * 构建新的声明
     *
     * @param ms             旧声明，
     * @param sqlCommandType sql类型
     * @param sql            sql字符串
     * @param boundSql       boundSql
     * @return 新的声明
     */
    public static MappedStatement buildMappedStatement(MappedStatement ms, SqlCommandType sqlCommandType, String sql,
                                                       BoundSql boundSql) {

        DataScopeSqlSource sqlSource = new DataScopeSqlSource(ms.getConfiguration(), sql, boundSql);

        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(),
                                                                      sqlSource,
                                                                      sqlCommandType);
        builder.resource(ms.getResource());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.fetchSize(ms.getFetchSize());
        builder.timeout(ms.getTimeout());
        builder.statementType(ms.getStatementType());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        builder.resultOrdered(ms.isResultOrdered());
        builder.keyGenerator(ms.getKeyGenerator());
        if (null != ms.getKeyProperties() && ms.getKeyProperties().length > 0) {
            builder.keyProperty(String.join(",", ms.getKeyProperties()));
        }
        if (null != ms.getKeyColumns() && ms.getKeyColumns().length > 0) {
            builder.keyColumn(String.join(",", ms.getKeyColumns()));
        }
        builder.databaseId(ms.getDatabaseId());
        builder.lang(ms.getLang());
        if (null != ms.getResultSets() && ms.getResultSets().length > 0) {
            builder.resultSets(String.join(",", ms.getResultSets()));
        }
        return builder.build();
    }

    /**
     * 数据权限添加
     *
     * @param select            查询对象
     * @param args              executor.query参数
     * @param dataScopeProperty 数据权限对象
     */
    private static void where(PlainSelect select, Object[] args, DataScopeProperty dataScopeProperty) {
        if (dataScopeProperty == null || dataScopeProperty.getColumns() == null || dataScopeProperty.getColumns()
                                                                                                    .isEmpty()) {
            return;
        }
        Expression where = select.getWhere();
        FromItem fromItem = select.getFromItem();
        Alias alias = fromItem.getAlias();
        boolean useAlias = alias != null && !StringUtils.isBlank(alias.getName());
        for (DataColumnProperty column : dataScopeProperty.getColumns()) {
            if (where == null) {
                EqualsTo andExpression = new EqualsTo(new LongValue(1), new LongValue(1));
                where = new Parenthesis(andExpression);
                select.setWhere(where);
            }
            Column columnExpression = new Column(column.getAliasDotName(useAlias));
            Expression de = null;
            // SqlKeyword
            switch (column.getOperator().toLowerCase()) {
                case "=":
                    de = whereEquals(select, column, columnExpression);
                    break;
                case ">":
                    de = whereG(select, column, columnExpression);
                    break;
                case ">=":
                    de = whereGe(select, column, columnExpression);
                    break;
                case "<":
                    de = whereL(select, column, columnExpression);
                    break;
                case "<=":
                    de = whereLe(select, column, columnExpression);
                    break;
                case "like":
                    de = whereLike(select, column, columnExpression);
                    break;
                case "in":
                    de = whereIn(select, column, columnExpression);
                    break;
                case "no in":
                    de = whereNotIn(select, column, columnExpression);
                    break;
                default:
                    break;
            }
            if (de != null) {
                select.setWhere(de);
            }
        }
    }

    private static Expression whereLe(PlainSelect select, DataColumnProperty column, Column columnExpression) {
        MinorThanEquals m = new MinorThanEquals();
        m.setLeftExpression(columnExpression);
        m.setRightExpression(new StringValue(String.valueOf(column.getValue())));
        return new AndExpression(select.getWhere(), m);
    }

    private static Expression whereL(PlainSelect select, DataColumnProperty column, Column columnExpression) {
        MinorThan m = new MinorThan();
        m.setLeftExpression(columnExpression);
        m.setRightExpression(new StringValue(String.valueOf(column.getValue())));
        return new AndExpression(select.getWhere(), m);
    }

    private static Expression whereGe(PlainSelect select, DataColumnProperty column, Column columnExpression) {
        GreaterThanEquals g = new GreaterThanEquals();
        g.setLeftExpression(columnExpression);
        g.setRightExpression(new StringValue(String.valueOf(column.getValue())));
        return new AndExpression(select.getWhere(), g);
    }

    private static Expression whereG(PlainSelect select, DataColumnProperty column, Column columnExpression) {
        GreaterThan g = new GreaterThan();
        g.setLeftExpression(columnExpression);
        g.setRightExpression(new StringValue(String.valueOf(column.getValue())));
        return new AndExpression(select.getWhere(), g);
    }

    private static Expression whereNotIn(PlainSelect select, DataColumnProperty column, Column columnExpression) {
        if (column.getValue() instanceof Collection) {
            InExpression inExpression = getInExpression(select, column, columnExpression);
            inExpression.setNot(true);
            return new AndExpression(select.getWhere(), inExpression);
        } else {
            log.warn("数据权限字段：{}设置的值非集合类型：{}", column.getName(), column.getValue());
        }
        return null;
    }

    private static Expression whereLike(PlainSelect select, DataColumnProperty column, Column columnExpression) {
        LikeExpression likeExpression = new LikeExpression();
        likeExpression.setLeftExpression(columnExpression);
        likeExpression.setRightExpression(new StringValue(String.valueOf(column.getValue())));
        return new AndExpression(select.getWhere(), likeExpression);
    }

    private static Expression whereIn(PlainSelect select, DataColumnProperty column, Column columnExpression) {
        if (column.getValue() instanceof Collection) {
            InExpression inExpression = getInExpression(select, column, columnExpression);
            return new AndExpression(select.getWhere(), inExpression);
        } else {
            log.warn("数据权限字段：{}设置的值非集合类型：{}", column.getName(), column.getValue());
        }
        return null;
    }

    private static Expression whereEquals(PlainSelect select, DataColumnProperty column, Column columnExpression) {
        EqualsTo andExpression = new EqualsTo();
        andExpression.withLeftExpression(columnExpression);
        andExpression.withRightExpression(new StringValue(column.getValue().toString()));
        return new AndExpression(select.getWhere(), andExpression);
    }

    private static InExpression getInExpression(PlainSelect select, DataColumnProperty column,
                                                Column columnExpression) {
        Collection<Object> value = (Collection<Object>) column.getValue();
        List<Expression> itemList = value.stream().map(d -> new StringValue(d.toString()))
                                         .collect(Collectors.toList());
        return new InExpression(columnExpression, new ExpressionList(itemList));
    }

}